﻿using System.Drawing;
using System.Drawing.Imaging;
using Windows.Win32;
using Windows.Win32.Graphics.Gdi;
using Avalonia;
using Avalonia.Platform;
using Everywhere.Common;
using Everywhere.I18N;
using Everywhere.Interop;
using FlaUI.Core;
using FlaUI.Core.AutomationElements;
using FlaUI.Core.EventHandlers;
using FlaUI.UIA3;
using Serilog;
using Bitmap = Avalonia.Media.Imaging.Bitmap;
using PixelFormat = System.Drawing.Imaging.PixelFormat;
using Point = System.Drawing.Point;
using Size = System.Drawing.Size;
using Vector = Avalonia.Vector;

namespace Everywhere.Windows.Interop;

public partial class VisualElementContext(IWindowHelper windowHelper) : IVisualElementContext
{
    private static readonly UIA3Automation Automation = new();
    private static readonly ITreeWalker TreeWalker = Automation.TreeWalkerFactory.GetContentViewWalker();

    public event IVisualElementContext.KeyboardFocusedElementChangedHandler? KeyboardFocusedElementChanged
    {
        add
        {
            if (_keyboardFocusedElementChangedHandler is null)
            {
                _focusChangedEventHandler = Automation.RegisterFocusChangedEvent(element =>
                {
                    if (_keyboardFocusedElementChangedHandler is not { } handler) return;
                    handler(TryCreateVisualElement(() => element));
                });
            }

            _keyboardFocusedElementChangedHandler += value;
        }
        remove
        {
            _keyboardFocusedElementChangedHandler -= value;
            if (_focusChangedEventHandler is not null)
            {
                Automation.UnregisterFocusChangedEvent(_focusChangedEventHandler);
                _focusChangedEventHandler = null;
            }
        }
    }

    public IVisualElement? KeyboardFocusedElement => TryCreateVisualElement(Automation.FocusedElement);

    private IVisualElementContext.KeyboardFocusedElementChangedHandler? _keyboardFocusedElementChangedHandler;
    private FocusChangedEventHandlerBase? _focusChangedEventHandler;


    public IVisualElement? ElementFromPoint(PixelPoint point, PickElementMode mode = PickElementMode.Element)
    {
        switch (mode)
        {
            case PickElementMode.Element:
            {
                return TryCreateVisualElement(() => Automation.FromPoint(new Point(point.X, point.Y)));
            }
            case PickElementMode.Window:
            {
                IVisualElement? element = TryCreateVisualElement(() => Automation.FromPoint(new Point(point.X, point.Y)));
                while (element is AutomationVisualElementImpl { IsTopLevelWindow: false })
                {
                    element = element.Parent;
                }

                return element;
            }
            case PickElementMode.Screen:
            {
                var hMonitor = PInvoke.MonitorFromPoint(new Point(point.X, point.Y), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
                return hMonitor == HMONITOR.Null ? null : new ScreenVisualElementImpl(hMonitor);
            }
        }

        return null;
    }

    public IVisualElement? ElementFromPointer(PickElementMode mode = PickElementMode.Element)
    {
        return !PInvoke.GetCursorPos(out var point) ? null : ElementFromPoint(new PixelPoint(point.X, point.Y), mode);
    }

    public Task<IVisualElement?> PickElementAsync(PickElementMode mode) => VisualElementPicker.PickAsync(windowHelper, mode);

    private static AutomationVisualElementImpl? TryCreateVisualElement(Func<AutomationElement?> factory)
    {
        try
        {
            if (factory() is { } element) return new AutomationVisualElementImpl(element);
        }
        catch (Exception ex)
        {
            Log.ForContext<VisualElementContext>().Error(
                new HandledException(ex, new DirectResourceKey("Failed to get AutomationElement")),
                "Failed to get AutomationElement");
        }

        return null;
    }

    private static bool IsAutomationException(Exception ex) =>
        ex.GetType().Namespace?.StartsWith("FlaUI.", StringComparison.Ordinal) == true;

    /// <summary>
    /// Captures a screenshot of the specified rectangle on the screen.
    /// </summary>
    /// <param name="rect"></param>
    /// <returns></returns>
    private static Bitmap CaptureScreen(PixelRect rect)
    {
        var gdiBitmap = new System.Drawing.Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
        using (var graphics = Graphics.FromImage(gdiBitmap))
        {
            graphics.CopyFromScreen(rect.X, rect.Y, 0, 0, new Size(rect.Width, rect.Height));
        }

        var data = gdiBitmap.LockBits(
            new Rectangle(0, 0, gdiBitmap.Width, gdiBitmap.Height),
            ImageLockMode.ReadOnly,
            PixelFormat.Format32bppArgb);
        Bitmap bitmap;
        try
        {
            bitmap = new Bitmap(
                Avalonia.Platform.PixelFormat.Bgra8888,
                AlphaFormat.Opaque,
                data.Scan0,
                rect.Size,
                new Vector(96d, 96d),
                data.Stride);
        }
        finally
        {
            gdiBitmap.UnlockBits(data);
        }

        return bitmap;
    }
}